/*
 * ComputeHeightLines.cpp
 *
 *  Created on: 8 Nov 2013
 *      Author: Patrick
 */

#include <vector>

#include <OgreImage.h>
#include <OgreColourValue.h>
#include <OgreVector3.h>
#include <OgreVector2.h>

#include "RegionLabeling.h"

#include "ComputeHeightLines.h"

namespace Engine {

//===================================================================================================

std::vector<MapObject*> ComputeHeightLines::computeHeightlines(
			HermiteSurface* surface,
			float minHeight,
			float maxHeight,
			float deltaHeight) {

	std::vector<MapObject*> mapObjects;
	int arrayWidth = surface->getXsize();
	int arrayHeight = surface->getYsize();

	/*
	 * create and initialise height-data
	 */
	std::vector<std::vector< float> > data;
	data.resize(arrayWidth);
	for(int i = 0; i < arrayWidth; i++) {
		data[i].resize(arrayHeight);
		for(int j = 0; j < arrayHeight; j++) {
			Ogre::Vector3 p;
			surface->getPosition(i, j, p);
			data[i][j] = p.z;
		}
	}

	/*
	 *	iterate through heightlevels
	 */
	for(int height = minHeight; height <= maxHeight; height += deltaHeight) {

		/*
		 * find regions
		 */
		std::vector<std::vector<int> > regions;
		std::vector<int> regionLabels;

		regions.resize(arrayWidth);
		for(int i = 0; i < arrayWidth; i++) {
			regions[i].resize(arrayHeight);
		}

		bool check = RegionLabeling::getRegions(&data, height, regions, regionLabels);

		/*
		 * apply marching squares
		 */
		if(check) {
			for(unsigned int label = 0; label < regionLabels.size(); label++) {
				int currentRegion = regionLabels[label];
				std::vector<Ogre::Vector3> pointList;
				Ogre::Vector2 startingPoint;
				Ogre::Vector2 currentPoint;

				/*
				 * fing start point
				 */
				startingPoint = findStartingPoint(&regions, currentRegion);
				currentPoint = startingPoint;

				/*
				 * walk around region
				 */
				StepDirection previousStep = None;
				StepDirection nextStep = None;
				bool continuous = true;

				do {
					int state = 0;

					if(currentPoint.x - 1 >= 0 && currentPoint.y - 1 >= 0)
						if(regions[currentPoint.x - 1][currentPoint.y - 1] == currentRegion)
							state |= 1;

					if(currentPoint.x < arrayWidth && currentPoint.y - 1 >= 0)
						if(regions[currentPoint.x][currentPoint.y - 1] == currentRegion)
							state |= 2;

					if(currentPoint.x - 1 >= 0 && currentPoint.y < arrayHeight)
						if(regions[currentPoint.x - 1][currentPoint.y] == currentRegion)
							state |= 4;

					if(currentPoint.x < arrayWidth && currentPoint.y < arrayHeight)
						if(regions[currentPoint.x][currentPoint.y] == currentRegion)
							state |= 8;

					previousStep = nextStep;
					Ogre::Vector3 point;
					bool isInsideBounds;

					switch(state) {
						case 5:
							nextStep = Up;
							isInsideBounds = interpolate(currentPoint, height, surface, DownrightDownleft, point);
							break;
						case 10:
							nextStep = Down;
							isInsideBounds = interpolate(currentPoint, height, surface, UpleftUpright, point);
							break;
						case 12:
							nextStep = Left;
							isInsideBounds = interpolate(currentPoint, height, surface, UprightDownright, point);
							break;
						case 3:
							nextStep = Right;
							isInsideBounds = interpolate(currentPoint, height, surface, DownleftUpleft, point);
							break;
						default:
							switch(state) {
								case 8:
									nextStep = Down;
									isInsideBounds = interpolate(currentPoint, height, surface, UprightDownright, point);
									break;
								case 1:
									nextStep = Up;
									isInsideBounds = interpolate(currentPoint, height, surface, DownleftUpleft, point);
									break;
								case 14:
									nextStep = Left;
									isInsideBounds = interpolate(currentPoint, height, surface, UpleftUpright, point);
									break;
								case 7:
									nextStep = Right;
									isInsideBounds = interpolate(currentPoint, height, surface, DownrightDownleft, point);
									break;
								case 2:
									nextStep = Right;
									isInsideBounds = interpolate(currentPoint, height, surface, UpleftUpright, point);
									break;
								case 4:
									nextStep = Left;
									isInsideBounds = interpolate(currentPoint, height, surface, DownrightDownleft, point);
									break;
								case 11:
									nextStep = Down;
									isInsideBounds = interpolate(currentPoint, height, surface, DownleftDownright, point);
									break;
								case 13:
									nextStep = Up;
									isInsideBounds = interpolate(currentPoint, height, surface, UprightDownright, point);
									break;
								default:
									float h1 = data[currentPoint.x - 1][currentPoint.y - 1];
									float h2 = data[currentPoint.x][currentPoint.y - 1];
									float h3 = data[currentPoint.x - 1][currentPoint.y];
									float h4 = data[currentPoint.x][currentPoint.y];
									float average = (h1 + h2 + h3 + h4) / 4.0f;
									switch(state) {
									case 9:
										if(previousStep == Right) {
											isInsideBounds = interpolate(currentPoint, height, surface, DownleftUpleft, point);
											if(average >= height)
												nextStep = Down;
											else
												nextStep = Up;
										} else {
											isInsideBounds = interpolate(currentPoint, height, surface, UprightDownright, point);
											if(average >= height)
												nextStep = Up;
											else
												nextStep = Down;
										}
										break;
									case 6:
										if(previousStep == Up) {
											isInsideBounds = interpolate(currentPoint, height, surface, DownrightDownleft, point);
											if(average >= height)
												nextStep = Right;
											else
												nextStep = Left;
										} else {
											isInsideBounds = interpolate(currentPoint, height, surface, UpleftUpright, point);
											if(average >= height)
												nextStep = Left;
											else
												nextStep = Right;
										}
										break;
									}
							}
					}

					if(isInsideBounds)
						pointList.push_back(point);
					else if(!pointList.empty()) {
						continuous = false;
						MapObject* object = new MapObject(pointList, false);
						mapObjects.push_back(object);
						pointList.clear();
					}

					switch(nextStep) {
						case Up: currentPoint.y--; break;
						case Down: currentPoint.y++; break;
						case Left: currentPoint.x--; break;
						case Right: currentPoint.x++; break;
						default: break;
					}

				} while(currentPoint.x != startingPoint.x || currentPoint.y != startingPoint.y);

				/*
				 * create MapObject
				 */
				if(!pointList.empty()) {
					MapObject* object = new MapObject(pointList, continuous);
					mapObjects.push_back(object);
				}
			}
		}//end of apply marching squares
	}

	return mapObjects;
}

//===================================================================================================

bool ComputeHeightLines::interpolate(
		Ogre::Vector2 currentPoint,
		float height,
		HermiteSurface* surface,
		Interpolations type,
		Ogre::Vector3 &point) {

	Ogre::Vector3 A; //low point
	Ogre::Vector3 B; //high point
	bool checkA, checkB;
	int xA = currentPoint.x;
	int yA = currentPoint.y;
	int xB = currentPoint.x;
	int yB = currentPoint.y;

	switch(type) {
	case UpleftUpright:
		xA--; yA--;
		yB--;
		break;
	case UprightUpleft:
		yA--;
		xB--; yB--;
		break;
	case UpleftDownleft:
		xA--; yA--;
		xB--;
		break;
	case DownleftUpleft:
		xA--;
		xB--; yB--;
		break;
	case DownrightDownleft:
		xB--;
		break;
	case DownleftDownright:
		xA--;
		break;
	case DownrightUpright:
		yB--;
		break;
	case UprightDownright:
		yA--;
		break;
	}

	checkA = surface->getPosition(xA, yA, A);
	checkB = surface->getPosition(xB, yB, B);

	if(!checkA || !checkB)
		return false;

	Ogre::Vector3 tA;
	Ogre::Vector3 tB;

	switch(type) {
	case UpleftUpright:
	case UprightUpleft:
	case DownrightDownleft:
	case DownleftDownright:
		surface->getHorizontalTangent(A.x, A.y, tA);
		surface->getHorizontalTangent(B.x, B.y, tB);
		break;
	case UpleftDownleft:
	case DownleftUpleft:
	case DownrightUpright:
	case UprightDownright:
		surface->getVerticalTangent(A.x, A.y, tA);
		surface->getVerticalTangent(B.x, B.y, tA);
		break;
	}

	float a = (height - A.z) / (B.z - A.z);
	Ogre::Vector3 b = (B - A) * a;
	Ogre::Vector3 c = A + b;
	point = c;
	return true;
}

Ogre::Vector2 ComputeHeightLines::findStartingPoint(
			std::vector<std::vector<int> >* regions,
			int currentRegion) {

	int arrayWidth = regions->size();
	int arrayHeight = regions[0].size();

	for(int i = 0; i < arrayWidth; i++) {
		if(regions->at(i)[0] == currentRegion) {
			return Ogre::Vector2(i, 0);
		}
		if(regions->at(arrayWidth-1-i)[arrayHeight-1] == currentRegion) {
			return Ogre::Vector2(arrayWidth-i, arrayHeight);
		}
	}

	for(int j = 0; j < arrayHeight; j++) {
		if(regions->at(arrayWidth-1)[j] == currentRegion) {
			return Ogre::Vector2(arrayWidth, j);
		}
		if(regions->at(0)[arrayHeight-1-j] == currentRegion) {
			return Ogre::Vector2(0, arrayHeight-j);
		}
	}

	for(int i = 1; i < arrayWidth-1; i++) {
		for(int j = 1; j < arrayHeight-1; j++) {
			if(regions->at(i)[j] == currentRegion) {
				return Ogre::Vector2(i, j);
			}
		}
	}

	return Ogre::Vector2(-1, -1);
}

} /* namespace Engine */
